package tuvienna.jade.participant;

import jade.core.behaviours.TickerBehaviour;

import java.math.BigDecimal;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import tuvienna.jade.CentralRandom;
import tuvienna.jade.Tools;

/**
 * This is a participant who will just look at each table and memorizes
 * the amount of gained knowledge. Once done exploring, he will choose
 * the best fitting table and stay there. The difference to the 
 * ExploringParticipant is that he will take into account that the 
 * tables may have changed in the meantime by discounting old information
 * and choose to reexamine tables.
 */
public class DynamicParticipantAgent extends ExploringParticipantAgent{

	private static final long serialVersionUID = -243421244576317835L;

	private double timeDisountFactor = 1.015; // factor to discount old memories (factor^t)

	private int oldAgeForTable = 15;
	
	
	private boolean verbose = false;
	private double moves;
	
	protected void setup() {
		super.setup();		
		
		Object[] args = getArguments();
		if (args != null) 
		{ 
			int cur = -1;
			while(++cur < args.length)
			{
				if("-timeDiscountFactor".equals(args[cur]) &&
				   ++cur < args.length)
					try	{	
						this.timeDisountFactor = Double.parseDouble(args[cur].toString());
					}
					catch(NumberFormatException e)	{
						System.out.println("Parsing timeDisountFactor failed. \n " +e.getMessage());
					}
				if("-oldAgeForTable".equals(args[cur]) &&
					++cur < args.length)
					try	{	
						this.oldAgeForTable = Integer.parseInt(args[cur].toString());
					}
					catch(NumberFormatException e)	{
						System.out.println("Parsing oldAgeForTable failed. \n " +e.getMessage());
					}
				if("-verbose".equals(args[cur]) &&
						++cur < args.length)
						try	{	
							this.verbose = Boolean.parseBoolean(args[cur].toString());
						}
						catch(NumberFormatException e)	{
							System.out.println("Parsing verbose failed. \n " +e.getMessage());
						}
			}		
		}
		
		
		
		addBehaviour(new TickerBehaviour(this, 1000) {
			private static final long serialVersionUID = 4784826386997399439L;
			protected void onTick() {
				for (TableInformation table:tables.values()) {
					addBehaviour(new RequestTableSizeBehaviour(getAgent(), table));
				}
			}
		});
	}
	
	private static double round(double d) {
		return ((int)(100*d))/100.0;
	}
	private static double round(BigDecimal bd) {
		return round(bd.doubleValue());
	}
	
	/**
	 * This method tries to find the best table. In early phases, it tries
	 * to explore the tables, if information is complete, it will just return
	 * the one were the agent got the most knowledge.
	 */
	protected TableInformation getBestTable() {
		TableInformation bestTable = null;
		StringBuffer reasoning = new StringBuffer();
		reasoning.append(getLocalName()+"@"+getTableInformation().getName()+"/"+
				getTableInformation().getSize()+ "/"
				+round(getTableInformation().getLastGainedKnowledge())+" M:+"+round(moves)+" :");
		
		

		// last one should not move
		if (getTableInformation().getSize() == 1) {
			bestTable = getTableInformation();
			reasoning.append("Cannot move - last one\r\n");
		} else {
		
		
			Collection<TableInformation> allTables = tables.values();
			Map<TableInformation, Double> unexploredTables = new HashMap<TableInformation, Double>();
			Map<TableInformation, Double> exploredTables = new HashMap<TableInformation, Double>();
			int sumParticipants = 0;
			for (TableInformation table:allTables) {
				if (table.getSize() > 0) {
					sumParticipants+= table.getSize();
					if (table.getLastGainedKnowledgeTime() > -1) { // explored table
						
						// discount knowledge by time 
						double score = table.getLastGainedKnowledge().doubleValue();
						int age = (currentTime) - table.getLastGainedKnowledgeTime();					
						score = score * Math.pow(timeDisountFactor, age);
						reasoning.append("age of info on table "+table.getName()+":"+age+"\r\n");
						
						
						double tooOldProbability = Tools.getTooOldProbability(age, oldAgeForTable);
						if (CentralRandom.nextDouble() < tooOldProbability) {
							unexploredTables.put(table, 0.1/table.getSize());
							reasoning.append("Table "+table.getName()+" info is old - reexplore("+tooOldProbability+")"+"\r\n");
						}				
						exploredTables.put(table, score);
						reasoning.append(table.getName() + "- explored("+age+"/"+score+")\r\n ");
	
					} else {
						double score = 1.0/table.getSize(); // small tables are usually better
						unexploredTables.put(table, score);
						reasoning.append(table.getName() + "- unexplored \r\n");
					}
				}
			}
	
			
			
			
			// randomly skip exploring part
			double exploreScore = 10.0 / sumParticipants; // 10 % may explore 
		    exploreScore *= Math.pow(0.33, moves/allTables.size()); // as agent gets older, he is less dynamic	
			// increase chances if table is useless
			if (this.getTableInformation().getLastGainedKnowledge().signum() == 0) {
				exploreScore = exploreScore * 4; 
			}
			boolean exploreUnexplored = exploredTables.size() == 0 ||  
					CentralRandom.nextDouble() < exploreScore;
			reasoning.append("Explore score:"+round(exploreScore)+"\r\n");	
			if (exploreUnexplored) {
				reasoning.append("Doing exploration by random"+"\r\n");
			}
			
			if (unexploredTables.size() > 0 && exploreUnexplored ) { 
				// explore tables
				bestTable = Tools.getRandomByScore(unexploredTables);
				reasoning.append("Exploring table "+bestTable.getName()+"\r\n");
			} else if (exploredTables.size() > 0){ // exploration done or skipped, choose best
				//print(exploredTables);
				bestTable = Tools.getBestByScore(exploredTables);
				reasoning.append("using table "+bestTable.getName()+"\r\n");						
			} else if (allTables.size() > 0) { 
				bestTable = Tools.getRandom(allTables);
				reasoning.append("No favorite - randomly chosing "+bestTable.getName()+"\r\n");	
			}
			
		}
		if (!bestTable.equals(getTableInformation())) {
			// it's a move
			
			moves = moves +1;
			if (verbose()) {
				System.out.println("move():"+reasoning.toString());
			}
		} else {
			if (verbose()) {
				System.out.print("Stay reason("+getTableInformation().getName()+"->"+bestTable.getName()+"):\r\n"+reasoning.toString());			
			}
		}
		return bestTable;
	}
	
	public static void _print(Map<TableInformation, Double> map ) {
		for (TableInformation entry:map.keySet()) {
			System.out.print(entry.getName()+":"+((int)(100*map.get(entry)))/100.0+"\t");
		}
		System.out.println();
	}

	
	protected boolean verbose() {
		return verbose;
	}
	


}
